/************************************************************************

								Heroes IV
					  Copyright 2000, The 3DO Company

	------------------------------------------------------------------
  						        resource_dir.cpp

	$Header: /game/resource_dir.cpp $

	$NoKeywords: $

 ************************************************************************/
// resource_dir.cpp: implementation of the t_resource_dir class.
//
//////////////////////////////////////////////////////////////////////

// kill the 255+ character identifier warning.  Templates tend to exceed that easily.
#include "precompiled.h"
#pragma warning( disable: 4786 )

#include "resource_dir.h"

#include <cassert>
#include <fstream>
#include <io.h>
#include <map>

#include "abstract_file.h"
#include "directory_changer.h"
#include "external_resource.h"
#include "os_file.h"
#include "resource_file.h"
#include "resource_file_dir.h"
#include "sized_int_types.h"
#include "shared_file.h"
#include "streambuf_operators.h"
#include "string_insensitive_compare.h"

namespace
{
typedef std::map< std::string, std::string, t_string_insensitive_less > t_alias_map;
typedef std::map< std::string, int, t_string_insensitive_less > t_resource_map;
}; // unnamed namespace


static t_alias_map    g_alias_map;
static t_resource_map g_directory_map;
static std::vector<t_resource_entry> g_directory;

/////////////////////////////////////////////////////////////////////////////
// these global functions encapsualtes project specific functionality
// on low level resource access allowing bink_wrapper classes to function
// in both projects without large changes in either project (game and resource editor)

t_abstract_file_ptr find_resource_file_handle( std::string resource_name )
{	
	t_os_file_ptr empty_windows_file(NULL);
	
	t_resource_entry const* res_entry_ptr =  find_resource( resource_name );

	if (res_entry_ptr == NULL) 
		return empty_windows_file;

	std::string filename = res_entry_ptr->file->get_filename();

	if (filename.empty()) 
		return empty_windows_file;

	t_os_file_ptr opened_windows_file (new t_os_file);
	
	if (opened_windows_file->open(filename.c_str(), "rb\0") == false)
		return empty_windows_file;
	
	return opened_windows_file;
}

int find_resource_offset( std::string resource_name ) 
{
	t_resource_entry const* res_entry_ptr =  find_resource( resource_name );

	if (res_entry_ptr == NULL) 
		return -1;

	return res_entry_ptr->offset;
}

// ---------------------------------------------------------------------
// add a new alias to list
// ---------------------------------------------------------------------
static void add_alias( std::string name, std::string alias )
{
	// check if the target is an alias.
	t_alias_map::const_iterator index = g_alias_map.find( name );

	while (index != g_alias_map.end())
	{
		// if it is, use the proper name instead of the alias.
		name = index->second;
		index = g_alias_map.find( name );
	}
	if (string_insensitive_compare( name, alias ) == 0)
		return;

	g_alias_map.insert( t_alias_map::value_type( alias, name ) );
}

// ---------------------------------------------------------------------
// resource directory.  Query to get a resource file.
// ---------------------------------------------------------------------
void close_resource_directory()
{
	g_directory.clear();
}

// ---------------------------------------------------------------------
// resource directory.  Query to get a resource file.
// ---------------------------------------------------------------------
// add all files in a folder as potential resources
void add_resource_folder( char const* name )
{
	_finddata_t      file_data;
	int              handle;
	t_resource_entry entry;
	t_resource_map::iterator item;
	int                      index;

	t_directory_changer directory_changer( name );

	handle = _findfirst( "*.h4d", &file_data );
	if (handle != -1)
	{
		do
		{
			// check if an alias exists.
			if (g_alias_map.count( file_data.name ) > 0)
				continue;

			// check if a directory entry exists.
			item = g_directory_map.find( file_data.name );
			if (item != g_directory_map.end())
				continue;

			if (file_data.attrib & _A_SUBDIR)
				continue;

			std::filebuf file;

			if (!file.open( file_data.name, std::ios::in | std::ios::binary ))
				continue;

			entry.name = file_data.name;
			entry.offset = 0;

			// try and read it.
			t_resource_file_dir directory;

			if (directory.read( file ))
			{
				// new-style .h4d with directory.
				if (directory.get_size() < 1)
					continue;

				if (!directory[0].aliased_name.empty())
				{
					add_alias( directory[0].aliased_name, file_data.name );
					continue;
				}
				entry.offset = directory[0].data_position;
				file.pubseekpos( entry.offset + directory[0].get_length() - 4 );
				entry.size = get<t_uint32>( file );
			}
			entry.file = new t_external_resource( name, file_data.name );
			index = g_directory.size();
			g_directory.push_back( entry );
			g_directory_map.insert( std::make_pair( entry.name, index ));
		} while (_findnext( handle, &file_data ) == 0);
	}
}

// ---------------------------------------------------------------------
// resource directory.  Query to get a resource file.
// ---------------------------------------------------------------------
// add a resource aggregate file
bool add_resource_file( char const* name )
{
	// open the resource file
	t_shared_file_ptr file( new t_shared_file( name , "rb" , k_windows_file_sys) );
	t_streambuf_ptr   stream( new t_shared_file_buffer( file ));

	if (file->get_abstract_file()->is_open()==false) 
		return false;

	// read directory
	t_resource_file_dir			directory;
	t_resource_entry			entry;
	t_resource_map::iterator	item;
	int							index;

	if (!directory.read( *stream ))
		return false;

	int i;

	g_directory.reserve( g_directory.size() + directory.get_size()  );

	for (i = 0; i < directory.get_size(); i++)
	{
		// check if an alias exists
		if (g_alias_map.count( directory[i].name ) > 0)
			continue;

		// check if a directory entry exists
		item = g_directory_map.find( directory[i].name );
		if (item != g_directory_map.end())
			continue;

		if (!directory[i].aliased_name.empty())
		{
			add_alias( directory[i].aliased_name, directory[i].name );
			continue;
		}
		entry.name = directory[i].name;
		entry.offset = directory[i].data_position;
		entry.file = new t_resource_file( file );
		stream->pubseekpos( entry.offset + directory[i].get_length() - 4 );
		entry.size = directory[i].get_uncompressed_size();
		index = g_directory.size();
		g_directory.push_back( entry );
		g_directory_map.insert( std::make_pair( entry.name, index ));
	}

	return true;
}

// ---------------------------------------------------------------------
// resource directory.  Query to get a resource file.
// ---------------------------------------------------------------------
t_resource_entry const* find_resource( std::string name )
{
	t_alias_map::const_iterator alias = g_alias_map.find( name );

	while ( alias != g_alias_map.end() )
	{
		name = alias->second;
		alias = g_alias_map.find( name );
	}

	t_resource_map::iterator item = g_directory_map.find( name );

	if (item == g_directory_map.end())
		return 0;
	return &g_directory[item->second];
}

t_resource_entry const* get_resource( int arg )
{
	return &g_directory[arg];
}

int get_resource_count()
{
	return g_directory.size();
}
